#include "cachelab.h"
#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

// 定义缓存行结构体
typedef struct {
    int valid;      // 有效位，1表示有效，0表示无效
    long tag;       // 标签位
    int lru_counter; // LRU计数器，用于实现LRU替换策略
} CacheLine;

// 定义缓存组结构体
typedef struct {
    CacheLine *lines; // 指向缓存行数组的指针
} CacheSet;

// 全局变量，用于存储缓存参数和统计结果
int s = 0; // 组索引位数 (S = 2^s 组)
int E = 0; // 每组行数
int b = 0; // 块偏移位数 (B = 2^b 字节/块)
char *trace_file = NULL; // 内存访问跟踪文件路径
int verbose = 0; // 是否启用详细输出模式

int hit_count = 0;   // 命中次数
int miss_count = 0;  // 未命中次数
int eviction_count = 0; // 替换次数

CacheSet *cache = NULL; // 缓存结构体指针

/**
 * @brief 打印程序使用帮助信息
 */
void print_usage() {
    printf("Usage: ./csim-ref [-hv] -s <num> -E <num> -b <num> -t <file>\n");
    printf("Options:\n");
    printf("  -h         Print this help message.\n");
    printf("  -v         Optional verbose flag.\n");
    printf("  -s <num>   Number of set index bits.\n");
    printf("  -E <num>   Number of lines per set.\n");
    printf("  -b <num>   Number of block offset bits.\n");
    printf("  -t <file>  Trace file.\n");
    printf("Examples:\n");
    printf("  linux>  ./csim-ref -s 4 -E 1 -b 4 -t traces/yi.trace\n");
    printf("  linux>  ./csim-ref -v -s 8 -E 2 -b 4 -t traces/yi.trace\n");
    exit(0);
}

/**
 * @brief 初始化缓存结构
 * 根据命令行参数s, E, b分配和初始化缓存
 */
void init_cache() {
    int S = 1 << s; // 计算组的数量 S = 2^s
    cache = (CacheSet *)malloc(sizeof(CacheSet) * S); // 为所有组分配内存
    if (cache == NULL) {
        fprintf(stderr, "Error: Failed to allocate memory for cache sets.\n");
        exit(1);
    }

    for (int i = 0; i < S; i++) {
        cache[i].lines = (CacheLine *)malloc(sizeof(CacheLine) * E); // 为每组分配缓存行内存
        if (cache[i].lines == NULL) {
            fprintf(stderr, "Error: Failed to allocate memory for cache lines in set %d.\n", i);
            // 释放之前已分配的内存
            for (int j = 0; j < i; j++) {
                free(cache[j].lines);
            }
            free(cache);
            exit(1);
        }
        for (int j = 0; j < E; j++) {
            cache[i].lines[j].valid = 0; // 初始化有效位为0 (无效)
            cache[i].lines[j].tag = 0;   // 初始化标签为0
            cache[i].lines[j].lru_counter = 0; // 初始化LRU计数器为0
        }
    }

}

/**
 * @brief 释放缓存占用的内存
 */
void free_cache() {
    int S = 1 << s; // 计算组的数量 S = 2^s
    for (int i = 0; i < S; i++) {
        free(cache[i].lines); // 释放每组的缓存行内存
    }
    free(cache); // 释放缓存组内存
}

/**
 * @brief 模拟一次内存访问
 * @param addr 内存地址
 */
void simulate_cache_access(long addr) {
    // 从地址中提取标签和组索引
    long tag = addr >> (s + b); // 提取标签位
    int set_index = (addr >> b) & ((1L << s) - 1); // 提取组索引位

    CacheSet *current_set = &cache[set_index]; // 获取当前组

    int hit_line_idx = -1; // 命中行的索引
    int empty_line_idx = -1; // 空闲行的索引
    int lru_line_idx = 0; // LRU行的索引
    int max_lru_counter = -1; // LRU计数器的最大值

    // 遍历当前组的所有缓存行
    for (int i = 0; i < E; i++) {
        // 查找命中
        if (current_set->lines[i].valid && current_set->lines[i].tag == tag) {
            hit_line_idx = i;
            break;
        }
        // 查找空闲行
        if (!current_set->lines[i].valid && empty_line_idx == -1) {
            empty_line_idx = i;
        }
        // 查找LRU行
        if (current_set->lines[i].lru_counter > max_lru_counter) {
            max_lru_counter = current_set->lines[i].lru_counter;
            lru_line_idx = i;
        }
    }

    // 更新所有有效行的LRU计数器
    for (int i = 0; i < E; i++) {
        if (current_set->lines[i].valid) {
            current_set->lines[i].lru_counter++;
        }
    }

    if (hit_line_idx != -1) { // 命中
        hit_count++;
        current_set->lines[hit_line_idx].lru_counter = 0; // 命中行LRU计数器清零
        if (verbose) {
            printf(" hit");
        }
    } else { // 未命中
        miss_count++;
        if (verbose) {
            printf(" miss");
        }

        if (empty_line_idx != -1) { // 有空闲行
            current_set->lines[empty_line_idx].valid = 1;
            current_set->lines[empty_line_idx].tag = tag;
            current_set->lines[empty_line_idx].lru_counter = 0; // 新行LRU计数器清零
        } else { // 无空闲行，需要替换
            eviction_count++;
            if (verbose) {
                printf(" eviction");
            }
            current_set->lines[lru_line_idx].valid = 1;
            current_set->lines[lru_line_idx].tag = tag;
            current_set->lines[lru_line_idx].lru_counter = 0; // 被替换行LRU计数器清零
        }
    }
}

int main(int argc, char **argv)
{
    char c;
    // 使用getopt解析命令行参数
    while ((c = getopt(argc, argv, "s:E:b:t:hv")) != -1) {
        switch (c) {
            case 's':
                s = atoi(optarg); // 获取组索引位数
                break;
            case 'E':
                E = atoi(optarg); // 获取每组行数
                break;
            case 'b':
                b = atoi(optarg); // 获取块偏移位数
                break;
            case 't':
                trace_file = optarg; // 获取跟踪文件路径
                break;
            case 'h':
                print_usage(); // 打印帮助信息
                exit(0);
            case 'v':
                verbose = 1; // 启用详细模式
                break;
            default:
                print_usage(); // 无效参数，打印帮助信息
                exit(1);
        }
    }

    // 检查所有必需参数是否已提供
    if (s == 0 || E == 0 || b == 0 || trace_file == NULL) {
        printf("%s: Missing required command line argument\n", argv[0]);
        print_usage();
        exit(1);
    }

    // 初始化缓存
    init_cache();

    FILE *pTraceFile;
    char line[1000]; // 假设每行最长1000字符
    char operation; // 操作类型 (L/S/M)
    long address;   // 内存地址
    int size;       // 访问大小 (字节)

    pTraceFile = fopen(trace_file, "r"); // 打开跟踪文件
    if (pTraceFile == NULL) {
        fprintf(stderr, "Error: Could not open trace file %s\n", trace_file);
        exit(1);
    }

    // 逐行读取跟踪文件
    while (fgets(line, sizeof(line), pTraceFile) != NULL) {
        if (line[0] == 'I') { // 忽略指令加载行
            continue;
        }

        // 解析每行数据
        sscanf(line, " %c %lx,%d", &operation, &address, &size);

        if (verbose) {
            printf("%c %lx,%d", operation, address, size);
        }

        // 模拟内存访问
        simulate_cache_access(address);

        // 如果是修改操作 (M)，则需要两次访问：一次读取，一次写入
        if (operation == 'M') {
            simulate_cache_access(address);
        }

        if (verbose) {
            printf("\n");
        }
    }

    fclose(pTraceFile); // 关闭跟踪文件

    // 释放缓存内存
    free_cache();

    // 打印最终统计结果
    printSummary(hit_count, miss_count, eviction_count);
    return 0;
}
